#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
//正式开始学习文件有关的知识：
//1，首先：文件=内容+属性  本质是研究进程与文件的关系
//文件分为打开的和没有打开的文件，打开文件必须先加载到内存  没有打开的文件放在磁盘中
//文件与进程的关系是1对多，应该进程可以打开很多文件，并且进程会维护一个文件指针数组来管理打开的文件
void test1()
{
  FILE* fp =fopen("log.txt","w");//打开一个文件，以写的方式，如果文件不存在就创建,文件默认在当前路径下创建
  //文件打开失败返回的是NULL
  if(fp==NULL)
  {
    return;
  }
  //常用的文件写入：
  const char* message="hello,world\n";
  //将目标字符串，第二个参数是写入的长度，第三个参数：写入次数，第四个参数：写入的目标文件
  fwrite(message,strlen(message),1,fp);//不用写入结尾的\0，文件中是不认\0作为结尾的

  //每个进程打开的时候默认打开了stdin（键盘）,stdout,stderr（显示器）
  fprintf(stdout,"%s",message);//就是向显示器写入
  //说明显示器，键盘这些外设也是对应的一个文件

  //文件使用完要关闭：
  fclose(fp);



}

//2,补充一种根据比特位来设置参数的方法：也叫做标志位传参
#define ONE (1<<0)//1左移0位代表一个参数
#define TWO (1<<1)//也就是第二位bit位设置为1，代表第二个参数（真实值是2）
#define THREE (1<<2)//真实值是4
#define FOUR (1<<3)

void show(int flags)//这样控制传进来参数就可以相当于同时传参多个值
{
  if(flags&ONE)printf("hello 1\n");
  if(flags&TWO)printf("hello 2\n");
  if(flags&THREE)printf("hello 3\n");
  if(flags&FOUR)printf("hello 4\n");
  //假如flags传的是3，那么相当于传的是bit位第0，第1位的参数为1的值
  //这种方法实现了控制bit位作为参数的实现
  
}
void test2()
{
  //传参也是使用定义好的参数并且可以同时传多个参数激活多个条件：
  show(ONE);//可以只激活一个条件
  show(ONE|THREE);//也可以使用按位或的方式激活多个条件

}

//3，所有的对文件的操作其实都不是我们用户能够直接触碰的，都是操作系统做到的，我们用户只能通过系统调用来请求操作系统帮我们实现对应的操作
//系统调用是操作系统给我们用户提供的接口：
//所以：所有的库函数都是封装了系统调用的
//要包含对应的头文件：
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
//学习一些系统调用接口：
void test3()
{
  //设置的文件权限还要收到掩码的制约也就是umask，默认是0002
  //我们可以手动改变：
  umask(0);

  //打开文件fopen底层也是对系统调用接口open的封装：
  //传参的方法就是使用bit位进行传参的方法，要使用对应定义的宏参数，可以同时选择多个参数的组合
  int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);//就是打开文件已覆盖的方式打开，不存在就创建，这就是fopen的底层使用的系统调用接口，这里就是实现了以"w"的方式打开文件
  //返回值是进程维护的文件指针数组的序号
  //以追加的方式打开文件的底层使用的是：open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  if(fd<0)
  {
    printf("open file fail\n");
    return;
  }
  const char*a="aaa";
  write(fd,a,strlen(a));//这里write也是系统调用，fwrite的实现底层就是调用这个接口

  close(fd);//close也是系统调用，fclose也是调用这个函数
}



//4，访问文件的本质：
void test4()
{
  //应该进程的pcb也就是建立的task_struct中维护了一个files_struct*file指针，指向该进程打开的文件指针数组struct file*fd array[]，打开一个文件就是把文件的地址添加到该数组中，并且填在没有占用的最小下标处
  //被管理的文件之间也是双向链表式的结构相连起来
  //默认打开的三个文件的编号：stdin(键盘):0,stdout（显示器）:1, stderr(显示器):2
  
  write(1,"hello\n",6);//向显示器打印了hello

  char buffer[1024];
  //read就是读取的系统调用，这里就是对0号文件读取，也就是向键盘读取,读取到buffer中
  ssize_t s=read(0,buffer,sizeof(1024));
  //返回的是写入的字符个数
  buffer[s]='\0';
  printf("%s\n",buffer);

  //文件结构体内封装了fileno自己的文件序号，可以使用文件指针直接访问：
  FILE* fp =fopen("log.txt","w");
  printf("FILE_log.txt_no:%d\n",fp->_fileno);//打印结果是3，也证实了被打开的文件的序号是从最小的没有被占用的序号开始使用
  //对于默认的三个标准指针也可以访问自己的序号：
  printf("stdin_no:%d\n",stdin->_fileno);
  printf("stdout_no:%d\n",stdout->_fileno);
  printf("stderr_no:%d\n",stderr->_fileno);
  

  fclose(fp);
	
}


//5，重定向的原理：就是把进程的文件指针表中重定向的目标的指针地址覆盖到被重定向的文件的指针数组中的位置，这样本来输出给在x序号的A文件的内容就变为了输出给x序号对应的B文件，因为B文件的指针地址覆盖了A文件的指针地址：
//例如：echo "hello"是默认输出给显示器的，但是echo "hello">log.txt 就是把log.txt对应序号的地址覆盖到1号地址上，这样输出到1号文件的内容就到了log.txt上去
void test5()
{
  //重定向使用的系统调用借口是dup2
  //dup2(int oldfd,int newfd)//注意：是oldfd覆盖到newfd的位置
  int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  dup2(fd,1);//把log.txt文件的地址覆盖了显示器对应序号的数组中的地址，这样printf是向1号位置的文件打印的，就被重定向为打印到了log.txt文件中
  printf("hello,hxk\n");
  fprintf(stdout,"YOU AREMINE\n");//stdout是指向1号位置的指针，现在位置为1的位置地址被覆盖，所以stdout指针打印内容也到了log.txt上

}

//注意：进程历史打开的文件和各种重定向关系都与未来的进程替换无关

//如果一开始就把1号文件关闭，那么1号文件的位置空出，那么新打开的文件就自动使用最小的没有使用的序号就是1，所以这样也完成了重定向

#define FILE_MODE 0666
#define SIZE 1024
#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_ALL 4

//6,封装自己的库函数：(底层使用系统调用接口)
typedef struct IO_FILE 
{
  int fileno_;//文件序号
  int flag_;//设置文件是什么刷新模式

  //c语言中文件缓冲区额度模拟，也说明了每个文件都有自己的缓冲区
  char outbuffer[SIZE];//模拟c语言在每个文件结构体中提供了一个c语言的用户级缓冲区
  
  int out_pos;//记录正在读取文件中的位置
}FILE_;
//函数声明：
FILE_ *fopen_(const char*filename,const char*flag);
int fwrite_(FILE_*fp,const char*s,int len);
void fclose_(FILE_*fp);
void fflush_(FILE_*fp);
void test6()
{
  //用自己封装的库函数实现文件io的功能
  FILE_*fp=fopen_("test.txt","w");
  if(fp==NULL)return;
  const char*msg="hello,hxk\n";
  const char*msg2="hahahaha\n";
  fwrite_(fp,msg,strlen(msg));
  fwrite_(fp,msg2,strlen(msg2));
  fclose_(fp);
  
}
//函数实现：
FILE_*fopen_(const char*filename,const char*flag)
{
  int f=0;
  int fd=-1;
  if(strcmp(flag,"w")==0)//如果是以写的方式打开文件：
  {
    f=(O_CREAT|O_WRONLY|O_TRUNC);
    fd=open(filename,f,FILE_MODE);//本质是用系统调用open实现
  }
  else if(strcmp(flag,"a")==0)
  {
    f=(O_CREAT|O_WRONLY|O_APPEND);
    fd=open(filename,f,FILE_MODE);
  }
  else if(strcmp(flag,"r")==0)
  {
    f=(O_RDONLY);
    fd =open(filename,f);
  }
  else 
  {
    return NULL;
  }
  if(fd==-1)return NULL;
  FILE_*fp=(FILE_*)malloc(sizeof(FILE_));
  if(fp==NULL)
  {
    perror("mallloc fail");
    return NULL;
  }
  //其实c语言中的文件指针FILE*就是维护一个含有对应文件的序号和文件信息的结构体而已
  fp->fileno_=fd;
  
  //初始化结构体中的数据：
  fp->out_pos=0;
  fp->flag_=FLUSH_LINE;//使用标志位传参
  return fp;
}
int fwrite_(FILE_*fp,const char*s,int len)
{
  //写的底层调用的是write
  
  //例如我们s为："abcd\n" 
  
  //第一步就是对当前文件此时文件的位置进行写入：
  memcpy(&fp->outbuffer[fp->out_pos],s,len);
  fp->out_pos+=len;//更新文件内位置

  //第二步判断缓冲模式，使用标志位传参的方式：
  if(fp->flag_&FLUSH_NOW)//无缓冲：
  {
    //立即写入：
    //write(fp->fileno_,fp->outbuffer,fp->out_pos);
    fflush_(fp);
  }
  else if(fp->flag_&FLUSH_LINE)
  {
    if(fp->outbuffer[fp->out_pos-1]=='\n')
    {
      //遇到\n才会进行行刷新：
      //write(fp->fileno_,fp->outbuffer,fp->out_pos);
      fflush_(fp);
    }
  }
  else if(fp->flag_&FLUSH_ALL)
  {
    if(fp->out_pos==SIZE)
    {
      //write(fp->fileno_,fp->outbuffer,fp->out_pos);
      fflush_(fp);
    }
  }
  return len;//返回成功写入的字符个数
}

void fflush_(FILE_*fp)//设置一个刷新函数
{
  if(fp->out_pos>0)
  {
    write(fp->fileno_,fp->outbuffer,fp->out_pos);
    fp->out_pos=0;//刷新后就要把位置重新回到0
  }
}

void fclose_(FILE_*fp)
{
//fclose底层使close：
  if(fp==NULL)return;
  close(fp->fileno_);
  free(fp);//文件结构体申请在堆区要自己手动释放
}

int main()
{
test6();



  return 0;
}
